热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

玩意|当前位置_RecyclerView添加Header的正确方式

篇首语:本文由编程笔记#小编为大家整理,主要介绍了RecyclerView添加Header的正确方式相关的知识,希望对你有一定的参考价值。看了一下博客目录,

篇首语:本文由编程笔记#小编为大家整理,主要介绍了RecyclerView添加Header的正确方式相关的知识,希望对你有一定的参考价值。


看了一下博客目录,已经有好几篇博客是关于RecyclerView的,不过对于这么一款强大的控件,我还是要再写一篇博客来学习一下,这篇博客的主题是《为RecyclerView添加header》,当然在看完这篇博客后,相信添加Footer你也应该能够学会。话说在这么多新控件中为何RecyclerView备受开发者的喜爱?这还是因为在android发展到今天基本上还没有像RecyclerView这么灵活的一个玩意,鉴于他的灵活以及强大,很多人(包括我)已经开始抛弃ListViewGridView转为RecyclerView了,再使用过RecyclerView和被善变的需求折磨后,我相信会有越来越多的人转到RecyclerView的使用上。


问题

好了,废话不多说了,这篇博客我们要解决的问题有:



  1. 如何为RecyclerView添加Header
  2. 如何让Header适配各种LayoutManager
  3. 在有Header的情况下,我们的分割线该怎么画
  4. 作为一个懒惰的程序员,如何将这些做到最简便


如果为RecyclerView添加Header

大家在使用ListView的时候可以很轻松的添加headers, 但是不知道大家发现没有,RecyclerView和各种LayoutManager都没有哪个方法是为添加header而设立的,这个时候我们就开始思考如何为RecyclerView添加header了。 这里我们的解决方案和网上你能搜到的大多数方案一样,是通过控制AdapteritemType来设置的,思路就是根据不同的itemType去加载不同的布局

/**
* Created by qibin on 2015/11/5.
*/

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final int TYPE_HEADER &#61; 0;
public static final int TYPE_NORMAL &#61; 1;
private ArrayList mDatas &#61; new ArrayList<>();
private View mHeaderView;
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener li)
mListener &#61; li;

public void setHeaderView(View headerView)
mHeaderView &#61; headerView;
notifyItemInserted(0);

public View getHeaderView()
return mHeaderView;

public void addDatas(ArrayList datas)
mDatas.addAll(datas);
notifyDataSetChanged();

&#64;Override
public int getItemViewType(int position)
if(mHeaderView &#61;&#61; null) return TYPE_NORMAL;
if(position &#61;&#61; 0) return TYPE_HEADER;
return TYPE_NORMAL;

&#64;Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
if(mHeaderView !&#61; null && viewType &#61;&#61; TYPE_HEADER) return new Holder(mHeaderView);
View layout &#61; LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new Holder(layout);

&#64;Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
if(getItemViewType(position) &#61;&#61; TYPE_HEADER) return;
final int pos &#61; getRealPosition(viewHolder);
final String data &#61; mDatas.get(pos);
if(viewHolder instanceof Holder)
((Holder) viewHolder).text.setText(data);
if(mListener &#61;&#61; null) return;
viewHolder.itemView.setOnClickListener(new View.OnClickListener()
&#64;Override
public void onClick(View v)
mListener.onItemClick(pos, data);

);


public int getRealPosition(RecyclerView.ViewHolder holder)
int position &#61; holder.getLayoutPosition();
return mHeaderView &#61;&#61; null ? position : position - 1;

&#64;Override
public int getItemCount()
return mHeaderView &#61;&#61; null ? mDatas.size() : mDatas.size() &#43; 1;

class Holder extends RecyclerView.ViewHolder
TextView text;
public Holder(View itemView)
super(itemView);
if(itemView &#61;&#61; mHeaderView) return;
text &#61; (TextView) itemView.findViewById(R.id.text);


interface OnItemClickListener
void onItemClick(int position, String data);

这里我们重写了getItemViewType方法&#xff0c;并根据位置来返回不同的type&#xff0c;这个type是我们预先商定好的常量&#xff0c;接在onCreateViewHolder方法中来判断itemType&#xff0c;如果是header&#xff0c;则返回我们设置的headerView&#xff0c;否则正常加载item布局&#xff0c;相信大家对于上面的代码不会有任何疑问&#xff0c;接下来我们就在Activity中用一下试试看&#xff0c;

&#64;Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView &#61; (RecyclerView) findViewById(R.id.list);
mLayoutManager &#61; new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter &#61; new MyAdapter();
mRecyclerView.setAdapter(mAdapter);
mAdapter.addDatas(generateData());
setHeader(mRecyclerView);
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener()
&#64;Override
public void onItemClick(int position, String data)
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();

);
private void setHeader(RecyclerView view)
View header &#61; LayoutInflater.from(this).inflate(R.layout.header, view, false);
mAdapter.setHeaderView(header);

这里LayoutManager我们使用了LinearLayoutManager&#xff0c;并且给Adapter设置了一个header&#xff0c;运行一下
看看效果&#xff1a;

恩&#xff0c;还不错&#xff0c;item的点击事件也很完美&#xff0c;那接下来&#xff0c;我们将LayoutManager换成GridLayoutManager看看咋样。


为GridLayoutManager添加header

// mLayoutManager &#61; new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mLayoutManager &#61; new GridLayoutManager(this, 2);

哎哟&#xff0c;我的小心脏啊&#xff0c;快受不了了&#xff0c;这是什么玩意&#xff0c;我们的header竟然作为一个cell出现在了界面上&#xff0c;这完全不是我们想要的效果啊&#xff01; 冷静下来想想&#xff0c;肯定会有解决方法的吧。这时候我们就该引入一个不太常用的方法了&#xff1a;

gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;

);

我们解释一下这段代码&#xff0c;首先我们设置了一个SpanSizeLookup&#xff0c;这个类是一个抽象类&#xff0c;而且仅有一个抽象方法getSpanSize&#xff0c;这个方法的返回值决定了我们每个position上的item占据的单元格个数&#xff0c;而我们这段代码综合上面为GridLayoutManager设置的每行的个数来解释的话&#xff0c;
就是当前位置是header的位置&#xff0c;那么该item占据2个单元格&#xff0c;正常情况下占据1个单元格。那这段代码放哪呢&#xff1f; 为了以后的封装&#xff0c;我们还是在Adapter中找方法放吧。
我们在Adapter中再重写一个方法onAttachedToRecyclerView&#xff0c;

&#64;Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager &#61; recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager)
final GridLayoutManager gridManager &#61; ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;

);

这个时候我们再来看一下效果&#xff0c;

恩&#xff0c;这次达到我们的要求了&#xff0c;不过对于StaggeredGridLayoutManager我们还没做处理&#xff0c;而且我们还发现StaggeredGridLayoutManager中并没有像GridLayoutManager中这样的方法&#xff0c;我们还需要单独为StaggeredGridLayoutManager单独处理一下。


为StaggeredGridLayoutManager添加header

我们继续重写Adapter中另外一个方法。

&#64;Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp &#61; holder.itemView.getLayoutParams();
if(lp !&#61; null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
StaggeredGridLayoutManager.LayoutParams p &#61; (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(holder.getLayoutPosition() &#61;&#61; 0);

这里的处理方式是用通过LayoutParams&#xff0c;而且这里更简单&#xff0c;StaggeredGridLayoutManager.LayoutParams为我们提供了一个setFullSpan方法来设置占领全部空间&#xff0c;好开心&#xff0c;看一下StaggeredGridLayoutManager的效果&#xff0c;

啊&#xff0c; 怎么和上面的效果一样&#xff1f; 很简单嘛&#xff0c;我们的item都是等高的。


处理分隔符

这是我们开开心心的继续写代码&#xff0c;并且为我们的item添加了分隔符&#xff0c;分隔符我还是用的翔哥写的那个&#xff0c;毕竟翔哥写的太好了&#xff0c;而且我们没有必要重复造轮子&#xff0c;不过这时候问题出现了&#xff0c;相信你也肯定能猜到应该会出现问题了&#xff0c;因为不管我们怎么处理&#xff0c;header对于RecyclerView来说还是一个普普通通的item&#xff0c;这时候我们添加分割线&#xff0c;肯定也会对header产生影响&#xff0c;那下面&#xff0c;我们再来对翔哥的分割线改造一下吧。

public class GridItemDecoration extends RecyclerView.ItemDecoration
private static final int[] ATTRS &#61; new int[]android.R.attr.listDivider;
private Drawable mDivider;
private boolean hasHeader;
public GridItemDecoration(Context context)
final TypedArray a &#61; context.obtainStyledAttributes(ATTRS);
mDivider &#61; a.getDrawable(0);
a.recycle();

public GridItemDecoration(Context context, boolean header)
this(context);
hasHeader &#61; header;

...
&#64;Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state)
int position &#61; parent.getChildAdapterPosition(view);
int spanCount &#61; getSpanCount(parent);
int childCount &#61; parent.getAdapter().getItemCount();
int pos &#61; position;
if(hasHeader)
if(position &#61;&#61; 0)
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
return;
else
pos &#61; position - 1;


if (isLastColum(parent, pos, spanCount, childCount))
outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
else
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());


改造的地方是获取偏移量的方法我们换了一个&#xff0c;因为原来的那个已经过时了&#xff0c;而且&#xff0c;这里我们还加了一个boolean类型的hasHeader变量来表示是不是有header&#xff0c;如果hasHeader并且position为0&#xff0c;那么我们仅仅绘制底部的分割线&#xff0c;其他的地方不绘制&#xff0c;在有header的情况下&#xff0c;我们还需要将position减1&#xff0c;因为我们认为的第1个item其实是第2个。这个时候我们再来看看有分割线的效果。

看来我们的想法是对的&#xff0c;header部分除了底部有一个分割线外&#xff0c;并没有其他的分割线&#xff0c;这也完全符合我们的需求。


封装

这下好了&#xff0c;基本上完美的处理好了&#xff0c;可是难道我们对于不同的Adapter都需要写那么多代码吗&#xff1f; 对于一个懒程序员来说&#xff0c;这肯定是一个可怕的事情&#xff0c;所以&#xff0c;我们还需要对我们的Adapter进行封装&#xff0c;目的就是可以轻轻松松的写代码&#xff0c;

/**
* Created by qibin on 2015/11/5.
*/

public abstract class BaseRecyclerAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public static final int TYPE_HEADER &#61; 0;
public static final int TYPE_NORMAL &#61; 1;
private ArrayList mDatas &#61; new ArrayList<>();
private View mHeaderView;
private OnItemClickListener mListener;
public void setOnItemClickListener(OnItemClickListener li)
mListener &#61; li;

public void setHeaderView(View headerView)
mHeaderView &#61; headerView;
notifyItemInserted(0);

public View getHeaderView()
return mHeaderView;

public void addDatas(ArrayList datas)
mDatas.addAll(datas);
notifyDataSetChanged();

&#64;Override
public int getItemViewType(int position)
if(mHeaderView &#61;&#61; null) return TYPE_NORMAL;
if(position &#61;&#61; 0) return TYPE_HEADER;
return TYPE_NORMAL;

&#64;Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, final int viewType)
if(mHeaderView !&#61; null && viewType &#61;&#61; TYPE_HEADER) return new Holder(mHeaderView);
return onCreate(parent, viewType);

&#64;Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position)
if(getItemViewType(position) &#61;&#61; TYPE_HEADER) return;
final int pos &#61; getRealPosition(viewHolder);
final T data &#61; mDatas.get(pos);
onBind(viewHolder, pos, data);
if(mListener !&#61; null)
viewHolder.itemView.setOnClickListener(new View.OnClickListener()
&#64;Override
public void onClick(View v)
mListener.onItemClick(pos, data);

);


&#64;Override
public void onAttachedToRecyclerView(RecyclerView recyclerView)
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager &#61; recyclerView.getLayoutManager();
if(manager instanceof GridLayoutManager)
final GridLayoutManager gridManager &#61; ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup()
&#64;Override
public int getSpanSize(int position)
return getItemViewType(position) &#61;&#61; TYPE_HEADER
? gridManager.getSpanCount() : 1;

);


&#64;Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder)
super.onViewAttachedToWindow(holder);
ViewGroup.LayoutParams lp &#61; holder.itemView.getLayoutParams();
if(lp !&#61; null
&& lp instanceof StaggeredGridLayoutManager.LayoutParams)
StaggeredGridLayoutManager.LayoutParams p &#61; (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(holder.getLayoutPosition() &#61;&#61; 0);


public int getRealPosition(RecyclerView.ViewHolder holder)
int position &#61; holder.getLayoutPosition();
return mHeaderView &#61;&#61; null ? position : position - 1;

&#64;Override
public int getItemCount()
return mHeaderView &#61;&#61; null ? mDatas.size() : mDatas.size() &#43; 1;

public abstract RecyclerView.ViewHolder onCreate(ViewGroup parent, final int viewType);
public abstract void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, T data);
public class Holder extends RecyclerView.ViewHolder
public Holder(View itemView)
super(itemView);


public interface OnItemClickListener<T>
void onItemClick(int position, T data);

我们将BaseRecyclerAdapter抽象起来&#xff0c;并且提供两个抽象方法onCreateonBind用来创建holder和绑定数据&#xff0c;而对于header做的一系列工作&#xff0c;我们都放到了BaseRecyclerAdapter中&#xff0c;而继承BaseRecyclerAdapter后&#xff0c;我们仅仅关心我们的holder怎么创建和数据怎么绑定就ok。例如下面代码&#xff1a;

/**
* Created by qibin on 2015/11/7.
*/

public class MyAdapter extends BaseRecyclerAdapter<String>
&#64;Override
public RecyclerView.ViewHolder onCreate(ViewGroup parent, int viewType)
View layout &#61; LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new MyHolder(layout);

&#64;Override
public void onBind(RecyclerView.ViewHolder viewHolder, int RealPosition, String data)
if(viewHolder instanceof MyHolder)
((MyHolder) viewHolder).text.setText(data);


class MyHolder extends BaseRecyclerAdapter.Holder
TextView text;
public MyHolder(View itemView)
super(itemView);
text &#61; (TextView) itemView.findViewById(R.id.text);


这样我们再用起来就简单多了&#xff0c;对于这样的封装&#xff0c;我们还算满意&#xff0c;再做完添加header后&#xff0c;相信大家对于footer也有想法了&#xff0c;有想法就实现它吧&#xff0c;扩展一下BaseRecyclerAdapter就ok啦。

好了&#xff0c;这篇博客就到这里吧&#xff0c;最后是本文代码的下载。

代码下载&#xff0c;戳这里


推荐阅读
  • 技术分享:深入解析GestureDetector手势识别机制
    技术分享:深入解析GestureDetector手势识别机制 ... [详细]
  • 掌握Android UI设计:利用ZoomControls实现图片缩放功能
    本文介绍了如何在Android应用中通过使用ZoomControls组件来实现图片的缩放功能。ZoomControls提供了一种简单且直观的方式,让用户可以通过点击放大和缩小按钮来调整图片的显示大小。文章详细讲解了ZoomControls的基本用法、布局设置以及与ImageView的结合使用方法,适合初学者快速掌握Android UI设计中的这一重要功能。 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • 在Android应用开发中,实现与MySQL数据库的连接是一项重要的技术任务。本文详细介绍了Android连接MySQL数据库的操作流程和技术要点。首先,Android平台提供了SQLiteOpenHelper类作为数据库辅助工具,用于创建或打开数据库。开发者可以通过继承并扩展该类,实现对数据库的初始化和版本管理。此外,文章还探讨了使用第三方库如Retrofit或Volley进行网络请求,以及如何通过JSON格式交换数据,确保与MySQL服务器的高效通信。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 在Android开发中,实现多点触控功能需要使用`OnTouchListener`监听器来捕获触摸事件,并在`onTouch`方法中进行详细的事件处理。为了优化多点触控的交互体验,开发者可以通过识别不同的触摸手势(如缩放、旋转等)并进行相应的逻辑处理。此外,还可以结合`MotionEvent`类提供的方法,如`getPointerCount()`和`getPointerId()`,来精确控制每个触点的行为,从而提升用户操作的流畅性和响应性。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 设计实战 | 10个Kotlin项目深度解析:首页模块开发详解
    设计实战 | 10个Kotlin项目深度解析:首页模块开发详解 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • 在Cisco IOS XR系统中,存在提供服务的服务器和使用这些服务的客户端。本文深入探讨了进程与线程状态转换机制,分析了其在系统性能优化中的关键作用,并提出了改进措施,以提高系统的响应速度和资源利用率。通过详细研究状态转换的各个环节,本文为开发人员和系统管理员提供了实用的指导,旨在提升整体系统效率和稳定性。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
author-avatar
算错的账目
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有